package MyGui;

import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.awt.event.WindowEvent;
import java.text.NumberFormat;
import java.util.*;

import Engines.*;
import DistSys.*;
import Element.*;

public class RiskAnalysisDlg extends MyDlgFrame implements ActionListener{
    public final int M_LEFT     = 0;
    public final int M_BIGLEFT  = 1;
    public final int M_ALL      = 2;
    public final int M_BIGRIGHT = 3;
    public final int M_RIGHT    = 4;
    public final int NUM_M_BTN  = 5; //Total num of moving btn

    public final int G_BINSIZE  = 0;
    public final int G_MAIFI_WT = 1;
    public final int G_SAIFI_WT = 2;
    public final int G_SAIDI_WT = 3;
    public final int G_CONFID   = 4;
    public final int NUM_G_ITEM = 5; //total num of items in Graph box

    public final int R_MIN      = 0;
    public final int R_MAX      = 1;
    public final int R_MEAN     = 2;
    public final int R_VAR      = 3;
    public final int R_CONFID   = 4;
    public final int R_VALUE    = 5;
    public final int NUM_R_ITEM = 6; //Total num of items in Results box

    private Font font = GuiConst.fontDefault;
    private RiskGraphPanel graphPanel ;
    private JButton         btnGraph;
    private JButton[]       btnMove;
    private JButton         btnExport;
    private JTextField    tfNumSample;
    private ZoneComboBox  comboChoice;
    private JRadioButton  rbRadial, rbNetwork;
    private JButton       btnExec;
    private JTextField[]  tfGraph;
    private JLabel[]      lblResults;
    private JCheckBox checkShowLog, checkShowConfidence, checkShowCurve;
    private JTextField tfTickX, tfGridX, tfTickY, tfGridY;

    public double[][] engineResults;

    public RiskAnalysisDlg(MainFrame parent, String title) {
        super(parent, title);

        graphPanel = new RiskGraphPanel();
        JScrollPane scrollPane = new JScrollPane( graphPanel);
        scrollPane.setPreferredSize(new Dimension(480,300));
        scrollPane.setSize( new Dimension(480,300));
        //scrollPane.setCsetRowHeader();

        JPanel bottomPanel = createBottomPanel();

        this.getContentPane().setLayout( new BorderLayout(2,2));
        this.getContentPane().add(scrollPane, BorderLayout.CENTER);
        this.getContentPane().add(bottomPanel, BorderLayout.SOUTH);

        this.setDefaultCloseOperation(JDialog.HIDE_ON_CLOSE);
        this.setSize(500,600);
        this.setLocation(200,100);
        //this.setResizable(false);
        btnExec.addActionListener( this );
        btnGraph.addActionListener( this );
        btnExport.addActionListener( this );
        for(int i=0; i<NUM_M_BTN; i++){
            btnMove[i].addActionListener( this );
        }
    }

    private JPanel createMoveButtonPanel(){
        JPanel p = new JPanel( new FlowLayout(FlowLayout.LEFT) ); //Flow Layout
        JPanel p2 = new JPanel( new FlowLayout(FlowLayout.CENTER));
        btnMove = new JButton[ NUM_M_BTN ];
        btnMove[M_LEFT] = new JButton("<");
        btnMove[M_BIGLEFT] = new JButton("<<");
        btnMove[M_ALL] = new JButton("All");
        btnMove[M_BIGRIGHT] = new JButton(">>");
        btnMove[M_RIGHT] = new JButton(">");

        for(int i=0; i<NUM_M_BTN; i++){
            btnMove[i].setFont(font);
            p2.add( btnMove[i] );
        }

        btnGraph = new JButton("Re-graph");
        btnGraph.setFont( font );
        btnExport = new JButton("Export");
        btnExport.setFont( font );
        p.add( btnGraph);
        p.add( p2 );
        p.add( btnExport);
        return p;
    }//end of createMoveButtonPanel

    private JPanel createExecParaPanel(){
        JPanel p = new JPanel( new GridBagLayout() );
        p.setBorder( new TitledBorder("Parameter"));
        tfNumSample = new JTextField("100",6);
        JLabel lblNumSample = new JLabel("# Samples");
        comboChoice = new ZoneComboBox();
        rbRadial = new JRadioButton("Radial Analysis");
        rbNetwork = new JRadioButton("Network Analysis        ");
          ButtonGroup rbGroup = new ButtonGroup();
          rbGroup.add( rbRadial );
          rbGroup.add( rbNetwork );
          rbRadial.setSelected(true);
          rbNetwork.setEnabled(false);
        btnExec = new JButton("Execute");
        tfNumSample.setFont(font);
        lblNumSample.setFont(font);
        comboChoice.setFont( font );
        rbRadial.setFont( font );
        rbNetwork.setFont( font );
        btnExec.setFont( font );
        //tfNumSample.setInputVerifier( new IntVerifier());

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.weightx = gbc.weighty =1;
        gbc.fill = GridBagConstraints.BOTH;
        gbc.gridx=0; gbc.gridy=0; gbc.gridwidth=1; p.add( tfNumSample, gbc);
        gbc.gridx=1; gbc.gridy=0; gbc.gridwidth=1; p.add( lblNumSample, gbc);
        gbc.gridx=0; gbc.gridy=1; gbc.gridwidth=2; p.add( comboChoice, gbc);
        gbc.gridx=0; gbc.gridy=2; gbc.gridwidth=2; p.add( rbRadial, gbc);
        gbc.gridx=0; gbc.gridy=3; gbc.gridwidth=2; p.add( rbNetwork, gbc);
        gbc.gridx=0; gbc.gridy=4; gbc.gridwidth=2; p.add( btnExec, gbc);
        return p;
    }

    private JPanel createGraphParaPanel(){
        JPanel p = new JPanel( new GridLayout(5,1,1,1));
        p.setBorder( new TitledBorder("Graphing"));

        JLabel[] lblGraph = new JLabel[NUM_G_ITEM];
        lblGraph[G_BINSIZE] = new JLabel("Bin Size");
        lblGraph[G_MAIFI_WT] = new JLabel("MAIFI Weight");
        lblGraph[G_SAIFI_WT] = new JLabel("SAIFI Weight");
        lblGraph[G_SAIDI_WT] = new JLabel("SAIDI Weight");
        lblGraph[G_CONFID] = new JLabel("Confidence(%)");

        tfGraph = new JTextField[NUM_G_ITEM];
        tfGraph[G_BINSIZE] = new JTextField("0.2",6);
        tfGraph[G_MAIFI_WT] = new JTextField("0",6);
        tfGraph[G_SAIFI_WT] = new JTextField("0",6);
        tfGraph[G_SAIDI_WT] = new JTextField("1",6);
        tfGraph[G_CONFID]  = new JTextField("90",6);

        for(int i=0; i<NUM_G_ITEM; i++){
            tfGraph[i].setFont( font );
            //tfGraph[i].setInputVerifier( new DoubleVerifier() );
            lblGraph[i].setFont(font);
            p.add( tfGraph[i] ) ;
            p.add( lblGraph[i] );
        }
        return p;
    }

    private JPanel createResultsPanel(){
        JPanel p = new JPanel( new GridBagLayout() );
        p.setBorder( new TitledBorder("Results"));

        JLabel[] lblResDesc = new JLabel[NUM_R_ITEM];
        lblResDesc[R_MIN] = new JLabel("Minimum Value");
        lblResDesc[R_MAX] = new JLabel("Maximum Value");
        lblResDesc[R_MEAN] = new JLabel("Mean Value");
        lblResDesc[R_VAR] = new JLabel("Standard Deviation");
        lblResDesc[R_CONFID] = new JLabel("Confidence(%)");
        lblResDesc[R_VALUE] = new JLabel("Value");

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.weightx = gbc.weighty = 1;
        gbc.fill = GridBagConstraints.BOTH;

        lblResults = new JLabel[NUM_R_ITEM];
        for(int i=0; i<NUM_R_ITEM; i++){
            lblResults[i] = new JLabel("   ----------  ");
            lblResults[i].setFont( font );
            lblResults[i].setForeground( Color.blue );
            gbc.gridx=0; gbc.gridwidth=1;
            gbc.gridy=i; p.add(lblResults[i], gbc);

            lblResDesc[i].setFont( font );
            gbc.gridx=1; gbc.gridwidth=2;
            gbc.gridy=i; p.add(lblResDesc[i], gbc);
        }
        return p;
    }

    private JPanel createOptionPanel(){
        font = GuiConst.fontSmall;
        JPanel p = new JPanel( new GridLayout(2,3,0,0));
        p.setBorder( new TitledBorder("Options"));

        tfTickX = new JTextField("5", 3);
        tfTickX.setFont(font);
        //tfTickX.setInputVerifier( new  PosIntVerifier() );
        tfGridX = new JTextField("5", 3);
        tfGridX.setFont(font);
        //tfGridX.setInputVerifier( new  PosIntVerifier() );
        tfTickY = new JTextField("5", 3);
        tfTickY.setFont(font);
        //tfTickY.setInputVerifier( new  PosIntVerifier() );
        tfGridY = new JTextField("5", 3);
        tfGridY.setFont(font);
        //tfGridY.setInputVerifier( new  PosIntVerifier() );

        JLabel lblOne, lblTwo;
        JPanel pSubTickX, pSubGridX, pSubTickY, pSubGridY;
        // X Tick
        pSubTickX = new JPanel( new FlowLayout( FlowLayout.LEFT ) );
        lblOne = new JLabel("Show X Tick every"); lblOne.setFont( font);
        lblTwo = new JLabel("bins");  lblTwo.setFont(font);
        pSubTickX.add( lblOne );
        pSubTickX.add( tfTickX );
        pSubTickX.add( lblTwo );
        // X Grid
        pSubGridX = new JPanel( new FlowLayout( FlowLayout.LEFT ) );
        lblOne = new JLabel("Show X Grid every"); lblOne.setFont( font);
        lblTwo = new JLabel("bins");  lblTwo.setFont(font);
        pSubGridX.add( lblOne );
        pSubGridX.add( tfGridX );
        pSubGridX.add( lblTwo );
        // Y Tick
        pSubTickY = new JPanel( new FlowLayout( FlowLayout.LEFT ) );
        lblOne = new JLabel("Show Y Tick every"); lblOne.setFont( font);
        lblTwo = new JLabel("%");  lblTwo.setFont(font);
        pSubTickY.add( lblOne );
        pSubTickY.add( tfTickY );
        pSubTickY.add( lblTwo );
        // Y Grid
        pSubGridY = new JPanel( new FlowLayout( FlowLayout.LEFT ) );
        lblOne = new JLabel("Show Y Grid every"); lblOne.setFont( font);
        lblTwo = new JLabel("%");  lblTwo.setFont(font);
        pSubGridY.add( lblOne );
        pSubGridY.add( tfGridY );
        pSubGridY.add( lblTwo );

        checkShowLog = new JCheckBox("Show Log");
        checkShowConfidence = new JCheckBox("Show Confidence");
        checkShowCurve = new JCheckBox("Show Curve Fit");
        checkShowLog.setFont(font); checkShowLog.setEnabled(false);
        checkShowConfidence.setFont(font); //checkShowConfidence.setEnabled(false);
        checkShowCurve.setFont(font); checkShowCurve.setEnabled(false);

        p.add( pSubTickX);  p.add( pSubTickY); p.add(checkShowConfidence );
        p.add( pSubGridX);  p.add( pSubGridY); p.add(checkShowCurve);
        return p;
    }

    private JPanel createBottomPanel(){
        JPanel p=new JPanel();
        GridBagLayout gridBagLayout = new GridBagLayout();
        GridBagConstraints gbc = new GridBagConstraints();
        p.setLayout( gridBagLayout );
        gbc.insets = new Insets(1,2,1,2);
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.anchor = GridBagConstraints.NORTH;
        gbc.weightx = gbc.weighty =1;
        gbc.gridx = 0; gbc.gridy=0; gbc.gridwidth=3; gbc.gridheight = 1;
        p.add( createMoveButtonPanel(), gbc);
        gbc.gridx = 0; gbc.gridy=1; gbc.gridwidth=1; gbc.gridheight = 8;
        p.add( createExecParaPanel(), gbc);
        gbc.gridx = 1; gbc.gridy=1; gbc.gridwidth=1; gbc.gridheight = 8;
        p.add( createGraphParaPanel(), gbc);
        gbc.gridx = 2; gbc.gridy=1; gbc.gridwidth=1; gbc.gridheight = 8;
        p.add( createResultsPanel(), gbc);
        gbc.gridx = 0; gbc.gridy=9; gbc.gridwidth=3; gbc.gridheight = 1;
        p.add( createOptionPanel(), gbc);
        return p;
    }

    public void actionPerformed(ActionEvent evt){
        Object o = evt.getSource();
        if( o == btnExec ){
            this.graphPanel.cleanImage();
            this.graphPanel.repaint();
            try {
                EngineProcessor.dZoneToEngine = comboChoice.getSelectedZone();
            } catch(ZoneComboBox.ZoneException e) {
                JOptionPane.showMessageDialog(mainFrame,
                  "'" + e.getMessage() + "' is not a valid zone.");
                return;
            }
            int mode = 4;
            int sample;
            double maifiWeight, saifiWeight, saidiWeight;
            try{
                sample = Integer.parseInt( tfNumSample.getText());
                maifiWeight = Double.parseDouble( tfGraph[G_MAIFI_WT].getText());
                saifiWeight = Double.parseDouble( tfGraph[G_SAIFI_WT].getText());
                saidiWeight = Double.parseDouble( tfGraph[G_SAIDI_WT].getText());
                if( sample <=0) {
                    JOptionPane.showMessageDialog(this, "# of sample must >0");
                    return;
                }
            }catch(Exception e){
                JOptionPane.showMessageDialog(this, "Input format error.");
                return;
            }

            //mainFrame.getContentPane().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
            SysConst.progress=0;
            UpdateProgressThread t = new UpdateProgressThread(
                mainFrame.btnPanel, ButtonPanel.ENGN_MODE);
            t.start();

            String strUrl = WebConst.getDistrelURL() +
                "?WebPath=" + WebConst.getEngineWebPath() +
                "&PadPath=" + Sys.personalInfo.getTempPath().getPath() +
                "&Mode=" + mode +
                "&Zone=" + EngineProcessor.getZoneAsEngineParamOld() +
                "&ID=0&P1=" + sample + "&Blocksag=1" +
                "&TargetMAIFI=" + maifiWeight + //?
                "&TargetSAIFI=" + saifiWeight + //?
                "&TargetSAIDI=" + saidiWeight;  //?

            graphPanel.setNumDisplayedBin(0); //updateGraph() will show all;
            mainFrame.menuBar.engineThread =
                new EngineThread(mainFrame, this, strUrl, EngineThread.ENG_RISK_RAD);
            mainFrame.menuBar.engineThread.start();
        }//end of btnExec
//        if( o == btnExec ){
//            this.graphPanel.cleanImage();
//            this.graphPanel.repaint();
//            try{
//                engineResults = EngineProcessor.readRiskResults();
//            } catch( Exception e){
//                JOptionPane.showMessageDialog(this, "Error in reading results.");
//                return;
//            }
//            graphPanel.setNumDisplayedBin(0); //updateGraph() will show all;
//            updateGraph();
//        }
        else if ( o== this.btnGraph ){
            updateGraph();
        } else if ( o == this.btnExport ) {
            try{
                EngineProcessor.readAndExportRiskResults();
            } catch( Exception e){
                JOptionPane.showMessageDialog(this, "Error in reading results.");
                return;
            }
        } else if ( o == this.btnMove[M_LEFT] ){
            graphPanel.decreaseNumDisplayedBin( RiskGraphPanel.SMALL_MOVE );
        } else if ( o == this.btnMove[M_BIGLEFT] ){
            graphPanel.decreaseNumDisplayedBin( RiskGraphPanel.BIG_MOVE );
        } else if ( o == this.btnMove[M_ALL] ){
            graphPanel.showAllBins();
        } else if ( o == this.btnMove[M_BIGRIGHT] ){
            graphPanel.increaseNumDisplayedBin( RiskGraphPanel.BIG_MOVE );
        } else if ( o == this.btnMove[M_RIGHT] ){
            graphPanel.increaseNumDisplayedBin( RiskGraphPanel.SMALL_MOVE );
        }
    }//end of actionPerformed()

    //update graph based on new parameters
    public void updateGraph(){
        if( engineResults == null ) { return; }
        double[] rawData = new double[ engineResults[0].length ];
        boolean bShowConfid;
        double  wMaifi, wSaifi, wSaidi, binSize, confid;
        int     iShowX, iShowY, iGridX, iGridY;
        try{
            bShowConfid = checkShowConfidence.isSelected();
            wMaifi = Double.parseDouble( tfGraph[G_MAIFI_WT].getText() );
            wSaifi = Double.parseDouble( tfGraph[G_SAIFI_WT].getText() );
            wSaidi = Double.parseDouble( tfGraph[G_SAIDI_WT].getText() );
            binSize = Double.parseDouble(tfGraph[G_BINSIZE].getText());
            confid  = Double.parseDouble( tfGraph[G_CONFID].getText() );
            iShowX  = Integer.parseInt( tfTickX.getText());
            iShowY  = Integer.parseInt( tfTickY.getText());
            iGridX  = Integer.parseInt( tfGridX.getText());
            iGridY  = Integer.parseInt( tfGridY.getText());
            if( ! checkInputValid(wMaifi, wSaifi, wSaidi, binSize, confid,
                iShowX, iShowY, iGridX, iGridY) ) { return; }
        }catch(Exception e){
            JOptionPane.showMessageDialog(this, e.toString());
            return;
        }

        for(int i=0; i< rawData.length; i++){
            rawData[i] += wMaifi * engineResults[0][i];
            rawData[i] += wSaifi * engineResults[1][i];
            rawData[i] += wSaidi * engineResults[2][i];
        }
        Arrays.sort( rawData, 0, rawData.length);
        updateResults( rawData, binSize, confid );

        graphPanel.setInputData( rawData, binSize, confid, iShowX, iShowY,
            iGridX, iGridY, bShowConfid);
        if( graphPanel.getNumDisplayedBin()==0 ) {
            graphPanel.showAllBins(); } //after pressing "execute" button
        else {
            graphPanel.updateImage();
            graphPanel.repaint(); }     //just refresh drawings
    }//end of updateGraph();

    private void updateResults(double[] rawData, double binSize, double confid){
        int index = (int) ((confid/100.0)*rawData.length);
        double confidValue = rawData[index-1]; //start at 0

        double mean = 0;
        for(int i=0; i< rawData.length; i++){
            mean += rawData[i];
        }
        mean /= rawData.length;
        double deviation=0;
        for(int i=0; i< rawData.length; i++){
            deviation += (rawData[i]- mean)*(rawData[i]- mean);
        }
        if( rawData.length < 1) { deviation = 0.0; }
        else { deviation /= (rawData.length-1); }
        deviation = Math.sqrt( deviation );

        NumberFormat nf = NumberFormat.getInstance();
        nf.setMaximumFractionDigits( 4 );
        lblResults[R_MIN ].setText( nf.format(rawData[0]) ); //min
        lblResults[R_MAX ].setText( nf.format(rawData[rawData.length-1]));//max
        lblResults[R_MEAN].setText( nf.format(mean));
        lblResults[R_VAR].setText( nf.format(deviation));
        lblResults[R_CONFID].setText( nf.format(confid));
        lblResults[R_VALUE].setText( nf.format( confidValue) );
    }

    private boolean checkInputValid(double wMaifi, double wSaifi, double wSaidi,
        double binSize, double confid, int iShowX, int iShowY,
        int iGridX, int iGridY ) {

        if( wMaifi < 0 || wSaifi < 0 || wSaidi < 0 ) {
            String msg="Please input non-negative numbers for weights.";
            JOptionPane.showMessageDialog(this, msg );
            return false;
        } else if( binSize <= 0 ){
            String msg="Please input a positive number for Bin Size.";
            JOptionPane.showMessageDialog(this, msg );
            return false;
        } else if( confid<0 || confid >100 ) {
            String msg="Please input a number in [0~100] for confidence.";
            JOptionPane.showMessageDialog(this, msg );
            return false;
        } else if( iShowX<=0 || iShowY<=0 || iGridX<=0 || iGridY<=0){
            String msg="Please input positive integers in Options";
            JOptionPane.showMessageDialog(this, msg );
            return false;
        }
        return true;
    }//end of checkInputValid()
}


class RiskGraphPanel extends JPanel{
    public static final int  PADDING_X = 30;
    public static final int  PADDING_Y = 20;
    public static final int  SMALL_MOVE = 1;
    public static final int  BIG_MOVE = 5;
    public static final int  NUM_SMALL_BIN = 5; //# of small bins in a bin;

    private double[]  adRawData;  //length = # of samples; sorted;
    private double    dBinSize;   //for stats
    private double    dConfid;
    private int       iShowX, iShowY, iGridX, iGridY;
    private boolean   bShowConfid;

    private int[]     aiNumInSmallBin;  //for d.f. curve
    private int[]     aiNumInBin;   //for p.d.f. curve
    private int       iMaxNumInBin; //max entry in aiNumInBin[]

    private Rectangle rectHistgram;
    private int       iNumDisplayedBin;  //for display
    private double    dColWidth;
    private int       iRowHeight;
    private int       iMaxY;

    private int       iStrHeight  = 10;   //hardcoded
    private int       iStrWidth   = 10;   //hardcoded
    private int       iTickLen    = 8;   //hardcoded
    private int       iTickThick  = 3;    //hardcoded
    private int       iInsetX     = 3;    //hardcoded

    private BasicStroke bsForDashLine;
    private Image image;

    public RiskGraphPanel(){
        super();
        rectHistgram = new Rectangle(PADDING_X, PADDING_Y, 0, 0);
        setBackground(Color.white);
        //other stuff
        if( image ==null){
            Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
            image= new BufferedImage(dim.width, dim.height,
                BufferedImage.TYPE_INT_ARGB);
        }
    }

    public void setInputData( double[] data, double binSize, double confid,
        int showX, int showY, int gridX, int gridY, boolean showConfid){

        this.adRawData = data;
        this.dBinSize = binSize;
        this.dConfid = confid;
        this.iShowX = showX;
        this.iShowY = showY;
        this.iGridX = gridX;
        this.iGridY = gridY;
        this.bShowConfid = showConfid;

        double dMax= adRawData[ adRawData.length -1]; //max

        int index;
        double dSmallBinSize = dBinSize/NUM_SMALL_BIN;
        int sz = (int) Math.ceil( dMax/dBinSize );
        sz = Math.max( sz, 1);          //sz maybe 0
        aiNumInBin = new int[sz];       //all initilized with 0's
        sz = sz * NUM_SMALL_BIN;        //small bins
        aiNumInSmallBin = new int[sz];  //all initilized with 0's
        for(int i=0; i< adRawData.length; i++){
            index = (int) Math.floor( (adRawData[i] )/dBinSize );
            aiNumInBin[index] += 1;
            index = (int) Math.floor( (adRawData[i] )/dSmallBinSize );
            aiNumInSmallBin[index] += 1;
        }

        for(int i=0; i< aiNumInBin.length; i++){
            if( i==0 || iMaxNumInBin < aiNumInBin[i] ) {
                iMaxNumInBin = aiNumInBin[i];
            }
        }
    }

    private void createStrokeForDashLine( Graphics2D g2){
        BasicStroke dftBS = (BasicStroke) g2.getStroke();
        float[] dashArray = { 5.0F, 3.0F };
        bsForDashLine = new BasicStroke(dftBS.getLineWidth(),
                                              dftBS.getEndCap(),
                                              dftBS.getLineJoin(),
                                              dftBS.getMiterLimit(),
                                              dashArray,  // customized dash
                                              dftBS.getDashPhase());

    }

    public void paintComponent(Graphics g){
        super.paintComponent(g);
        if( iNumDisplayedBin == 0 || adRawData==null ) { return; }
        g.drawImage(image, 0, 0, null);
    }

    public void cleanImage(){
        Graphics g = image.getGraphics();
        g.setColor( Color.white );
        g.fillRect(0,0,image.getWidth(this), image.getHeight(this));
    }

    public void updateImage(){
        Graphics g = image.getGraphics();
        cleanImage();

        rectHistgram.x = PADDING_X;
        rectHistgram.y = PADDING_Y;
        rectHistgram.width = ((int) this.getSize().getWidth()) - 2 * PADDING_X;
        rectHistgram.height= ((int) this.getSize().getHeight()) - 2* PADDING_Y;
        dColWidth = ((double)rectHistgram.width) / iNumDisplayedBin ;

        iMaxY = 5* (int) Math.ceil( 20.0*iMaxNumInBin/adRawData.length );
        //[0, 5, 10, 15, ...., 95, 100]

        g.setFont( GuiConst.fontDefault );
        paintMainGraph(g);
        paintGridX(g);
        paintGridY(g);
        if( bShowConfid ) { paintConfidence(g); }
        paintTickX(g);
        paintTickLeftY(g);
        paintTickRightY(g);
    }

    private void paintMainGraph(Graphics g){
        g.setColor( Color.black);
        g.drawRect(rectHistgram.x, rectHistgram.y,
            rectHistgram.width, rectHistgram.height);

        int originX = rectHistgram.x;
        int originY = rectHistgram.y;
        for(int j=0; j<iNumDisplayedBin; j++ ){
            if( j>= aiNumInBin.length ) { break;}
            double percentage = (100.0*aiNumInBin[j]/adRawData.length)/iMaxY;
            int colHeight = (int) ( percentage * rectHistgram.height);
            int colY = originY+rectHistgram.height-colHeight;
            int colX = (int) (originX + j * dColWidth);
            int colNextX = (int) (originX + (j+1) * dColWidth);
            g.setColor( Color.cyan );
            g.fillRect( colX, colY, (colNextX - colX ), colHeight );
            g.setColor( Color.black );
            g.drawRect( colX, colY, (colNextX - colX ), colHeight );
        }

        int xPrev=0, x=0, yPrev=0, y=0;
        double sum=0.0;
        double sumInPercent;
        g.setColor( Color.red );
        for(int j=0; j<aiNumInSmallBin.length; j++){
            sum += aiNumInSmallBin[j];
            sumInPercent = sum / adRawData.length;
            x = rectHistgram.x + (int) (j*dColWidth/NUM_SMALL_BIN);
            y = rectHistgram.y + (int) (rectHistgram.height *( 1.0 - sumInPercent));
            if( x < rectHistgram.x + rectHistgram.width ) {
                if( j > 0){ g.drawLine( xPrev, yPrev, x, y); }
                xPrev = x;
                yPrev = y;
            } else {
                g.drawLine( xPrev, yPrev, rectHistgram.x+rectHistgram.width, y);
                break;
            }
        }
    }

    private void paintConfidence(Graphics g ) {
        Graphics2D g2 =(Graphics2D) g;
        BasicStroke dftBS = (BasicStroke) g2.getStroke();
        if( bsForDashLine==null){ createStrokeForDashLine(g2); }
        g2.setStroke(bsForDashLine);      // update stroke

        int index = (int) ((dConfid/100.0)*adRawData.length);
        double confidValue = adRawData[index-1]; //start at 0
        int x = rectHistgram.x + (int) (dColWidth * (confidValue/dBinSize));
        int y = rectHistgram.y + (int) (rectHistgram.height *( 1.0-dConfid/100.0));
        g.setColor( Color.red );
        if( x < rectHistgram.x + rectHistgram.width ){
            g.drawLine( rectHistgram.x, y, x, y );
            g.drawLine( x, rectHistgram.y+rectHistgram.height, x, y);
        } else { //out of boundary
            g.drawLine( rectHistgram.x, y, rectHistgram.x+rectHistgram.width, y);
        }

        g2.setStroke(dftBS);      // set stroke back to default one
    }

    private void paintGridY(Graphics g){
        Graphics2D g2 =(Graphics2D) g;
        BasicStroke dftBS = (BasicStroke) g2.getStroke();
        if( bsForDashLine==null){ createStrokeForDashLine(g2); }
        g2.setStroke(bsForDashLine);      // update stroke
        g.setColor( Color.gray );

        //orgnX, orgnY ==> bottom-left point of the rect
        int orgnX = rectHistgram.x;
        int orgnY = rectHistgram.y + rectHistgram.height;
        int sz = iMaxY/iGridY;    //same as Math.floor(...)
        for(int j=0; j<=sz; j++){
            int yOffset = (int) (((double) j)*iGridY*rectHistgram.height/iMaxY);
            if( j!=0 && j*iGridY != iMaxY){
                g.drawLine( orgnX, orgnY - yOffset,
                            orgnX + rectHistgram.width, orgnY - yOffset );
            }
        }
        g2.setStroke( dftBS );
    }

    private void paintTickLeftY(Graphics g){
        String str;
        int orgnX = 0;
        int orgnY = rectHistgram.y + rectHistgram.height;
        int sz = iMaxY/iShowY;
        g.setColor(Color.blue);
        for(int j=0; j<=sz; j++){
            int yOffset = (int) (((double)j)*iShowY*rectHistgram.height/iMaxY);
            str = ""+ (j*iShowY) +"%";
            g.drawString( str, orgnX+iInsetX, orgnY - yOffset + iStrHeight/2 );
            g.fillRect( rectHistgram.x, orgnY-yOffset-1, iTickLen, iTickThick );
        }
    }

    private void paintTickRightY(Graphics g){
        String str;
        int orgnX = rectHistgram.x + rectHistgram.width;
        int orgnY = rectHistgram.y + rectHistgram.height;
        g.setColor(Color.red);
        for(int j=0; j<=5; j++){
            int yOffset = (int) (((double) j)*rectHistgram.height/5);
            str = ""+ (20*j) +"%";
            g.drawString( str, orgnX + iInsetX, orgnY - yOffset + iStrHeight/2 );
            g.fillRect( orgnX-iTickLen, orgnY-yOffset-1, iTickLen, iTickThick );
        }
    }

    private void paintTickX(Graphics g){
        String s;
        int x, y;
        NumberFormat nf = NumberFormat.getNumberInstance();
        nf.setMaximumFractionDigits(3);
        g.setColor( Color.black );
        for(int j=0; j<iNumDisplayedBin; j++){
            if( j% iShowX==0){
                s = nf.format(j*dBinSize);
                x = (int) ( rectHistgram.x + j*dColWidth);
                //top legend
                //y = rectHistgram.y;
                //g.fillRect( x-1, y, iTickThick, iTickLen);
                //g.drawString( s, x-iStrWidth/2, y);
                //bottom legend
                y = rectHistgram.y + rectHistgram.height;
                g.fillRect( x-1, y-iTickLen, iTickThick, iTickLen);
                g.drawString( s, x-iStrWidth/2, y+ iStrHeight );
            }
        }
    }

    private void paintGridX(Graphics g){
        Graphics2D g2 =(Graphics2D) g;
        BasicStroke dftBS = (BasicStroke) g2.getStroke();
        if( bsForDashLine==null){ createStrokeForDashLine(g2); }
        g2.setStroke(bsForDashLine);      // update stroke
        g.setColor( Color.gray );

        int x;
        int y1 = rectHistgram.y;
        int y2 = rectHistgram.y + rectHistgram.height;
        for(int j=0; j<iNumDisplayedBin; j++){
            if( j% iGridX==0){
                x = (int) ( rectHistgram.x + j*dColWidth);
                g.drawLine( x, y1, x, y2);
            }
        }
        g2.setStroke( dftBS );
    }

    public int getNumDisplayedBin(){ return this.iNumDisplayedBin; }

    public void setNumDisplayedBin(int numDisPlayedBin){
        this.iNumDisplayedBin = numDisPlayedBin;
    }

    public void increaseNumDisplayedBin( int numOfBin ){
        this.iNumDisplayedBin += numOfBin;
        updateImage();
        repaint();
    }

    public void decreaseNumDisplayedBin( int numOfBin ){
        if( this.iNumDisplayedBin > numOfBin ){ //if having more bins displayed
            this.iNumDisplayedBin -= numOfBin;
            updateImage();
            repaint();
        }
    }

    public void showAllBins(){
        if( adRawData != null ){ //if data is available
            this.iNumDisplayedBin = this.aiNumInBin.length;
            updateImage();
            repaint();
        }
    }
}
